package com.xxx.server.utils;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.xxx.server.common.ViewContentType;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Encoder;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.rowset.serial.SerialException;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author LiuYang
 * @date 2023/6/30 10:14
 * @className: MinioUtil
 * @Description: TODO
 * @Version 1.0
 */
@Data
@Component
public class MinioUtil {
    @Value("${minio.address}")
    private String address;
    @Value("${minio.accessKey}")
    private String accessKey;
    @Value("${minio.secretKey}")
    private String secretKey;
    @Value("${minio.bucketName}")
    private String bucketName;

    /**
     * 初始化minio
     * @return
     */
    public MinioClient getMinioClient() {
        try {
            return  MinioClient.builder()
                    .endpoint(address)
                    .credentials(accessKey,secretKey)
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 检查存储桶是否存在
     *
     * @param bucketName 存储桶名称
     * @return
     */
    public boolean bucketExists(String bucketName) {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return false;
        }
        boolean flag = false;
        try {
            flag = minioClient.bucketExists(BucketExistsArgs.builder()
                    .bucket(bucketName)
                    .build());
            if (flag) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 存储桶名称
     */
    public boolean makeBucket(String bucketName) {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return false;
        }
        try {
            boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            //存储桶不存在则创建存储桶
            if (!found) {
                // 如果不存在创建桶
                minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(bucketName)
                        .build());
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 上传文件
     *
     * @param file 上传文件
     * @return 成功则返回文件名，失败返回空
     */
    public boolean uploadFile(MultipartFile file,String fileName,String filePath) throws SerialException {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return false;
        }
        //创建存储桶
        boolean createFlag = makeBucket(bucketName);
        //创建存储桶失败
        if (!createFlag) {
            return false;
        }
        try {
            //得到文件流
            InputStream inputStream = file.getInputStream();
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(filePath+"/"+fileName)
                    .contentType(ViewContentType.getContentType(fileName))
                    .stream(inputStream,inputStream.available(),-1)
                    .build());

            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 上传文件
     *
     * @param file 上传文件
     * @return 成功则返回文件名，失败返回空
     */
    public boolean uploadFileByInputSteam(InputStream file,String fileName,String filePath){
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return false;
        }
        //创建存储桶
        boolean createFlag = makeBucket(bucketName);
        //创建存储桶失败
        if (!createFlag) {
            return false;
        }
        try {
            //得到文件流
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(filePath+"/"+fileName)
                    .contentType(ViewContentType.getContentType(fileName))
                    .stream(file,file.available(),-1)
                    .build());

            file.close();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 下载文件
     *
     * @param originalName 文件路径*/

    public InputStream downloadFile(String originalName, HttpServletResponse response) {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return null;
        }
        try {
            // 2. 获取对象的InputStream,并保存为文件
            InputStream file = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(originalName)
                            .build());
            // 赋值文件名字
            String filename = new String(originalName.getBytes("ISO8859-1"), StandardCharsets.UTF_8);
            if (StrUtil.isNotBlank(originalName)) {
                filename = originalName;
            }
            // 设置响应头
            response.setHeader("Content-Disposition", "attachment;filename=" + filename);
            ServletOutputStream servletOutputStream = response.getOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            // 赋值
            while ((len = file.read(buffer)) > 0) {
                servletOutputStream.write(buffer, 0, len);
            }
            servletOutputStream.flush();
            file.close();
            servletOutputStream.close();
            return file;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 删除文件
     *
     * @param fileName 文件路径
     * @return
     */
    public boolean deleteFile(String fileName) {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return false;
        }
        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * @description:获取文件预览接口
     * @date: 2022/8/18 9:44
     * @param: fileName: 文件名
     * @Param: bucketName: 桶名
     * @Param: previewExpiry: 预览到期时间（小时）
     * @return: java.lang.String
     **/
    public String getPreviewUrl(String fileName,Integer previewExpiry) {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return null;
        }
        if (StringUtils.isNotBlank(fileName)) {
            bucketName = StringUtils.isNotBlank(bucketName) ? bucketName : this.bucketName;
            try {
                minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
                if (null != previewExpiry){
                    return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(fileName)
                            .expiry(previewExpiry, TimeUnit.HOURS)
                            .build());
                }else {
                    return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(fileName)
                            .build());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 查询所有桶文件
     *
     * @return
     */
    public  List<String> getListBuckets() {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return null;
        }
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            return buckets.stream().map(bucket -> bucket.name()).collect(Collectors.toList());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }

    /**
     * 删除桶
     * @param
     * @return
     */
    public boolean deleteBucket() {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return false;
        }
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 批量删除文件
     * @param objects 对象名称
     * @return boolean
     */
    public List<DeleteError> deleteObject(List<String> objects) {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return null;
        }
        List<DeleteError> deleteErrors = new ArrayList<>();
        List<DeleteObject> deleteObjects = objects.stream().map(value -> new DeleteObject(value)).collect(Collectors.toList());
        Iterable<Result<DeleteError>> results =
                minioClient.removeObjects(
                        RemoveObjectsArgs
                                .builder()
                                .bucket(bucketName)
                                .objects(deleteObjects)
                                .build());
        try {
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                deleteErrors.add(error);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return deleteErrors;
    }

    /**
     * 获取单个桶中的所有文件对象名称
     *
     * @return {@link List}<{@link String}>
     */
    public  List<String> getBucketObjectName(String prefix) {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return null;
        }
        List<String> listObjectName = new ArrayList<>();
        try {
            Iterable<Result<Item>> myObjects = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(prefix)
                            .build());
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                String name = item.objectName();
                String substring = name.substring(name.indexOf(prefix)+prefix.length()-1);
                int indexOf = substring.indexOf("/");
                String nextSubstring = "";
                if (indexOf != -1) {
                    nextSubstring = substring.substring(indexOf+1);
                } else {
                    nextSubstring = substring;
                }
                listObjectName.add(nextSubstring);
            }
            return listObjectName;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取某个桶下某个对象的URL
     *
     * @param objectName 对象名 (文件夹名 )
     * @return
     */
    public  String getBucketObject(String objectName) throws Exception {
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return null;
        }
        // 删除之前先检查`my-bucket`是否存在。
        boolean found = makeBucket(bucketName);
        if (found) {
            final GetObjectResponse object = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return object.object();
        }
        return "";
    }

    /**
     * @description: 批量下载
     * @param: filenames: 多个文件名称
     * @Param: zip: 压缩包名称
     * @Param: res: 响应对象
     * @return: void
     **/
    public void batchDownload(List<String> filenames, String zip, HttpServletResponse res, HttpServletRequest req){
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return;
        }
        try {
            BucketExistsArgs bucketArgs = BucketExistsArgs.builder().bucket(bucketName).build();
            boolean bucketExists = minioClient.bucketExists(bucketArgs);
            BufferedOutputStream bos = null;
            res.reset();
            bos = new BufferedOutputStream(res.getOutputStream());
            ZipOutputStream out = new ZipOutputStream(bos);
            res.setHeader("Access-Control-Allow-Origin", "*");
            for (int i=0;i<filenames.size();i++) {
                GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName)
                        .object(filenames.get(i)).build();
                InputStream object = minioClient.getObject(objectArgs);
                byte buf[] = new byte[1024];
                int length = 0;
                res.setCharacterEncoding("utf-8");
                res.setContentType("application/force-download");// 设置强制下载不打开
                res.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
                res.setHeader("Content-Disposition", "attachment;filename=" + filenameEncoding(zip,req) + ".zip");
                out.putNextEntry(new ZipEntry(filenames.get(i)));
                while ((length = object.read(buf)) > 0) {
                    out.write(buf, 0, length);
                }
            }
            out.close();
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *  设置不同浏览器编码
     * @param filename 文件名称
     * @param request 请求对象
     */
    public static String filenameEncoding(String filename, HttpServletRequest request) throws UnsupportedEncodingException {
        // 获得请求头中的User-Agent
        String agent = request.getHeader("User-Agent");
        // 根据不同的客户端进行不同的编码

        if (agent.contains("MSIE")) {
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            BASE64Encoder base64Encoder = new BASE64Encoder();
            filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
        } else {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }

    /**
     * 获取文件流转化成base64
     * @param objectName
     * @return
     */
    public String getInputStream(String objectName){
        MinioClient minioClient = getMinioClient();
        if (minioClient == null) {
            return null;
        }
        try {
            InputStream inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
            // byte[] bytes1= IOUtils.toByteArray(inputStream);
            long start = System.currentTimeMillis();
            List<byte[]> bytes = readInputStreamInChunks(inputStream, 8192 * 16 * 4 * 2);
            long end = System.currentTimeMillis();
            System.out.println(end-start);
            byte[] bytes1 = combineByteArrays(bytes);
            Base64.Encoder encoder = Base64.getEncoder();
            String base = encoder.encodeToString(bytes1);
            return base;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static List<byte[]> readInputStreamInChunks(InputStream inputStream, int chunkSize) {
        List<byte[]> byteList = new ArrayList<>();
        try {
            byte[] data = new byte[chunkSize];
            int nRead;
            long start = System.currentTimeMillis();
            while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
                byte[] chunk = new byte[nRead];
                System.arraycopy(data, 0, chunk, 0, nRead);
                long end = System.currentTimeMillis();
                System.out.println(end-start);
                byteList.add(chunk);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return byteList;
    }

    public static byte[] combineByteArrays(List<byte[]> byteList) {
        int totalLength = byteList.stream().mapToInt(b -> b.length).sum();
        byte[] combined = new byte[totalLength];
        int currentPos = 0;
        for (byte[] array : byteList) {
            System.arraycopy(array, 0, combined, currentPos, array.length);
            currentPos += array.length;
        }
        return combined;
    }

    // /**
    //  * 根据指定大小压缩图片
    //  *
    //  * @param imageBytes 源图片字节数组
    //  * @param desFileSize 指定图片大小，单位kb
    //  * @param imageId 影像编号
    //  * @return 压缩质量后的图片字节数组
    //  */
    // public static byte[] compressPicForScale(
    //         byte[] imageBytes, long desFileSize, String imageId, Double quality) {
    //     if (imageBytes == null || imageBytes.length <= 0 || imageBytes.length < desFileSize * 1024) {
    //         return imageBytes;
    //     }
    //     long srcSize = imageBytes.length;
    //     try {
    //         ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
    //         ByteArrayOutputStream outputStream= new ByteArrayOutputStream(imageBytes.length);
    //         Thumbnails.of(inputStream).scale(1f).outputQuality(quality).toOutputStream(outputStream);
    //         imageBytes = outputStream.toByteArray();
    //         log.info(
    //                 "【图片压缩】imageId={} | 图片原大小={}kb | 压缩后大小={}kb",
    //                 imageId,
    //                 srcSize / 1024,
    //                 imageBytes.length / 1024);
    //     } catch (Exception e) {
    //         log.error("【图片压缩】msg=图片压缩失败!", e);
    //     }
    //     return imageBytes;
    // }

}
